import os
import fnmatch
import pickle
import string
import StringIO

from kivy.logger import Logger

from amgineirc.model.amgine.amgine import Amgine

__all__ = [
           'RulesModel',
           'ServersModel',
           'TrustedUsersModel']

class PersistentData(object):
    """
    base class for persistent data 
    """
    
    config_folder_name = 'my_configuration_files'

    def __init__(self, data_dir, filename, defaults):
        """
        data_dir is where the data files must be stored.
        filename is the name of the file for pickling
        ex: 'my_settings.pickled'

        defaults is a dict or list.
        it contains the default settings.
        """
        # create the file
        self.data_folder_path = os.path.join(data_dir, PersistentData.config_folder_name)
        self._create_path(data_dir, filename)
        if filename is not None:
            if os.path.exists(self._path):
                    f = open(self._path, 'rb')
                    try:
                        self.data = pickle.load(f)
                    except Exception as e:
                        self.data = defaults
                    f.close()
                    return
            else:
                # no such file
                self.data = defaults
                return
        else:
            # no filename
            self._path = None
            self.data = defaults
            return
            
    @property
    def missing_configuration_folder_message(self):
        return 'Reinstall the stick or card with your "%s" folder.' % (PersistentData.config_folder_name)
        
    def have_configuration_folder(self):
        return os.path.isdir(self.data_folder_path)
            
    def get_path(self):
        return self._path
                        
    def _create_path(self, data_dir, filename):
        # create the folder path
        if self.have_configuration_folder() is False:
            os.mkdir(self.data_folder_path)
        if filename is not None:
            # there is one file for this persistent object
            # object can create multiple files (filename is None)
            self._path = os.path.join(self.data_folder_path, filename)

    def save(self):
        if self.have_configuration_folder() is False:
            return False, 'Unable to find your configuration folder path.'
        elif self._path is not None and self.data is not None:
            return self._write_pickle_file(self._path, self.data)
        else:
            return False, 'Nothing to save.'

    def _write_pickle_file(self, path, obj):
        try:
            f = open(path, 'wb')
        except:
            return (False, 'Unable to open %s for writing.' % (path))
        try:
            pickle.dump(obj, f, 2)
        except:
            try:
                os.unlink(path)
            except:
                pass
            return (False, 'Unable to write to %s.' % (path))
        f.close()
        return(True, 'Data Saved at %s' % (path))


class ServersModel(PersistentData):
    def __init__(self, data_dir):
        PersistentData.__init__(self,
                                data_dir,
                                'servers.pickled',
                                {})
                                
    @property
    def default_record(self):
        return {'domain':'', 'port':6667, 'nick':'', 'nick_password':'', 'secure':False}
        
    def backup(self):
        return dict(self.data)
        
    def restore(self, backup):
        if isinstance(backup, dict):
            self.dict = backup

    def add_server(self, domain, port, secure, nick, nick_password):
        ok = domain not in self.data
        if ok:
            self.data[domain] = {'port':port,
                                 'nick':nick,
                                 'nick_password':nick_password,
                                 'secure':secure}
        return ok
            
    def set_server(self, old_domain, new_domain, port, secure, nick, nick_password):
        ok = old_domain in self.data
        if ok:
            self.data[new_domain] = {'port':port,
                                     'nick':nick,
                                     'nick_password':nick_password,
                                     'secure':secure}
            if old_domain != new_domain:
                del(self.data[old_domain])
        return ok

    def get_server(self, domain):
        if domain in self.data:
            return self.data[domain]
        else:
            return None


class TrustedUsersModel(PersistentData):
    def __init__(self, data_dir):
        PersistentData.__init__(self,
                                data_dir,
                                'trusted_users.pickled',
                                {})
                                
    def backup(self):
        return dict(self.data)
        
    def restore(self, backup):
        if isinstance(backup, dict):
            self.data = backup
            
    def using_server(self, server):
        keys = self.data.keys()
        for key in keys:
            if self.data[key]['server'] == server:
                return True
        return False

    @property
    def default_record(self):
        return {'nick':'', 'real_name':'', 'server':'', 'rule_name':''}

    def nick_list(self, server):
        return [self.data[i]['nick']\
                for i in self.data if self.data[i]['server'] == server]
        
    def add_user(self, nick, real_name, server, rule_name):
        key = nick.strip() + ':' + server.strip()
        ok = key not in self.data
        if ok:
            self.data[key] = {'nick':nick,
                              'real_name':real_name,
                              'server':server,
                              'rule_name':rule_name}
        return ok
        
    def set_user(self, nick, real_name, server, rule_name):
        key = nick.strip() + ':' + server.strip()
        ok = key in self.data
        if ok:
            self.data[key] = {'nick':nick,
                              'real_name':real_name,
                              'server':server,
                              'rule_name':rule_name}
        return ok
        
    def get_user(self, nick, server):
        key = nick.strip() + ':' + server.strip()
        if key in self.data:
            return self.data[key]
        else:
            return None
        
    def del_user(self, nick, server):
        key = nick.strip() + ':' + server.strip()
        if key in self.data:
            del(self.data[key])
        
    def nick_using_rule(self, rule_name):
        for i in self.data:
            if self.data[i]['rule_name'] == rule_name:
                return i
        return None

class RulesModel(PersistentData):

    def __init__(self, data_dir):
        """
        no file name because each rule has its own pickle file.
        data is a list
        """
        PersistentData.__init__(self, data_dir, None, [])
        self.fn_ext = '.rotary_encryption_pickled'
        self.good_chars = string.letters + string.digits + '_'
        self._load_rules()
                    
    def backup(self):
        return self.data[:]
        
    def restore(self, backup):
        if isinstance(backup, list):
            self.data = backup

    def get(self, name):
        if name in self.data:
            return self._get(self._name_to_path(self.data_folder_path, name))
        return None

    def unlink(self, name):
        if len(name) == 0:
            return (False, 'Missing name.')
        if self.is_valid_name(name) is False:
            return (False, '"' + name + '" is not a valid name.')
        if name not in self.data:
            return (False, 'There is no rotary encryption rule named "' + name + '".')
        i = self.data.index(name)
        del(self.data[i])
        path = self._name_to_path(self.data_folder_path, name)
        if os.path.exists(path) == False:
            return (False, 'There is no rotary encryption rule file at "' + path + '".')
        # everything should work
        os.unlink(path)
        return (True, 'The rotary encryption rule named "' + name + '" has been deleted.')

    def set(self, name, newname):
        if self.is_valid_name(newname) is False:
            return (False, 'Name must be letters and digits only.')
        if len(name) == 0:
            return (False, 'Missing name.')
        if len(newname) == 0:
            return (False, 'Missing new name.')
        if self.is_valid_name(name) is False:
            return (False, '"' + name + '" is not a valid name.')
        if self.is_valid_name(newname) is False:
            return (False, '"' + newname + '" is not a valid name.')
        if name not in self.data:
            return (False, 'There is no rotary encryption rule named "' + name + '".')
        spath = self._name_to_path(self.data_folder_path, name)
        if os.path.exists(spath) == False:
            return (False, 'The rotary encryption rule file at "' + spath + '" does not exist.')
        dpath = self._name_to_path(self.data_folder_path, newname)
        if os.path.exists(dpath) == True:
            return (False, 'The rotary encryption rule file at "' + dpath + '" already exists.')
        # everything should work
        i = self.data.index(name)
        self.data[i] = newname
        os.rename(spath, dpath)
        return (True, '"' + name + '" has been renamed "' + newname + '".')

    def create(self, name):
        if self.is_valid_name(name) is False:
            return (False, 'Name must be letters and digits only.')
        if len(name) == 0:
            return (False, 'Missing name.')
        if name in self.data:
            return (False, 'The rotary encryption rule named "' + name + '" already exists.')
        path = self._name_to_path(self.data_folder_path, name)
        if os.path.exists(path):
            return (False, 'A rotary encryption rule file at "' + path + '" already exists.')
        # everything should work
        rules = Amgine()
        f = open(path, 'wb')
        pickle.dump(rules, f, 2)
        f.close()
        self.data.append(name)
        return (True,'The rotary encryption rule named "' + name + '"was sucessfully created at "' + path + '".')

    def export_rule_to(self, rule, name, path):
        if not isinstance(rule, Amgine):
            ok = False
            message = 'Missing source rotary encryptionrule.'
        elif os.path.isdir(path) is False:
            return(False, 'Unable to find the folder path "%s".' % (self.data_folder_path))
        else:
            dpath = self._name_to_path(path, name)
            ok, message = self._write_pickle_file(dpath, rule)
        if ok:
            message = 'The rotary encryption rule named "%s" was successfully exported to "%s".' % (name, path)
        return (ok, message)
        
    def replace_rule(self, rule, name):
        if name in self.data:
            path = self._name_to_path(self.data_folder_path, name)
            f = open(path, 'wb')
            pickle.dump(rule, f, 2)
            f.close()
            return True
        else:
            return False
        
    def load_rule_from(self, spath):
        rule = self._get(spath)
        if rule is None:
            message = 'You can not import the file because it is not a rotary encryption rule.'
        else:
            message = ''
        return (rule, message)
        
    def import_rule(self, rule, name):
        message = 'You can not name the rotary encryption rule "%s: because ' % (name)
        if not self.is_valid_name(name):
            ok = False
            message += '"%s" is not a valid rotary encryption rule name.' % (name)
        elif self.have_configuration_folder() is False:
            ok = False
            message = 'Unable to find your configuration folder path.' 
        elif name in self.data:
            ok = False
            message += 'you already have a rotary encryption rule named %s.' % (name)
        else:
            dpath = self._name_to_path(self.data_folder_path, name)
            ok, message = self._write_pickle_file(dpath, rule)
            if ok:
                self.data.append(name)
        return (ok, message)

    def import_rule_from(self, spath, dest_rulename=''):
        message = 'You can not import the file %s because ' % (bn)
        bn = os.path.basename(spath)
        name = bn[:bn.rindex('.')]
        if not bn.endswith(self.fn_ext):
            message += '"%s" does not end with "%s".' % (bn, self.fn_ext)
            return(False, message)
        rule, msg = self.load_rule_from(spath)
        if rule is None:
            return (False, msg)
        else:
            if len(dest_rulename) == 0:
                dest_rulename = name
            return self.import_rule(rule, dest_rulename)
            
    def save(self, name):
        # there is no real list of rule names to pickle
        # the rule names are actually the names files which are picked rules.
        return True, ''
             
    def get_path(self):
        return self.data_folder_path

    def _load_rules(self):
        mask = '*' + self.fn_ext
        for p in os.listdir(self.data_folder_path):
            if fnmatch.fnmatch(p, mask):
                path = os.path.join(self.data_folder_path, p)
                if os.path.exists(path):
                    rules = self._get(path)
                    if rules is not None:
                        # fn = os.path.basename(p)
                        name = p[:p.rindex('.')]
                        self.data.append(name)
                    else:
                        # bad rules file so remove it
                        os.unlink(path)

    def _name_to_path(self, path, name):
        return os.path.join(path, name + self.fn_ext)

    def name_from_path(self, path):
        bn = os.path.basename(path)
        return bn[:bn.rindex('.')]

    def _get(self, path):
        f = open(path, 'rb')
        try:
            rules = pickle.load(f)
        except:
            rules = None
        f.close()
        if rules is not None and not isinstance(rules, Amgine):
            rules = None
        return rules
            
    def is_valid_name(self, name):
        if len(name) == 0:
            return False
        for c in name:
            if c not in self.good_chars:
                return False
        return True
